关闭某个 channel,会执行函数 closechan

    1. func closechan(c *hchan) {
    2. // 关闭一个 nil channel,panic
    3. if c == nil {
    4. panic(plainError("close of nil channel"))
    5. }
    6. // 上锁
    7. lock(&c.lock)
    8. // 如果 channel 已经关闭
    9. if c.closed != 0 {
    10. unlock(&c.lock)
    11. // panic
    12. panic(plainError("close of closed channel"))
    13. }
    14. // …………
    15. // 修改关闭状态
    16. c.closed = 1
    17. var glist *g
    18. // 将 channel 所有等待接收队列的里 sudog 释放
    19. for {
    20. // 从接收队列里出队一个 sudog
    21. sg := c.recvq.dequeue()
    22. // 出队完毕,跳出循环
    23. if sg == nil {
    24. break
    25. }
    26. // 如果 elem 不为空,说明此 receiver 未忽略接收数据
    27. // 给它赋一个相应类型的零值
    28. if sg.elem != nil {
    29. typedmemclr(c.elemtype, sg.elem)
    30. sg.elem = nil
    31. }
    32. if sg.releasetime != 0 {
    33. sg.releasetime = cputicks()
    34. }
    35. // 取出 goroutine
    36. gp := sg.g
    37. gp.param = nil
    38. if raceenabled {
    39. raceacquireg(gp, unsafe.Pointer(c))
    40. }
    41. // 相连,形成链表
    42. gp.schedlink.set(glist)
    43. glist = gp
    44. }
    45. // 将 channel 等待发送队列里的 sudog 释放
    46. // 如果存在,这些 goroutine 将会 panic
    47. for {
    48. // 从发送队列里出队一个 sudog
    49. sg := c.sendq.dequeue()
    50. if sg == nil {
    51. break
    52. }
    53. // 发送者会 panic
    54. sg.elem = nil
    55. if sg.releasetime != 0 {
    56. sg.releasetime = cputicks()
    57. }
    58. gp := sg.g
    59. gp.param = nil
    60. if raceenabled {
    61. raceacquireg(gp, unsafe.Pointer(c))
    62. }
    63. // 形成链表
    64. gp.schedlink.set(glist)
    65. glist = gp
    66. }
    67. // 解锁
    68. unlock(&c.lock)
    69. // Ready all Gs now that we've dropped the channel lock.
    70. // 遍历链表
    71. for glist != nil {
    72. // 取最后一个
    73. gp := glist
    74. // 向前走一步,下一个唤醒的 g
    75. glist = glist.schedlink.ptr()
    76. gp.schedlink = 0
    77. // 唤醒相应 goroutine
    78. goready(gp, 3)
    79. }
    80. }

    close 逻辑比较简单,对于一个 channel,recvq 和 sendq 中分别保存了阻塞的发送者和接收者。关闭 channel 后,对于等待接收者而言,会收到一个相应类型的零值。对于等待发送者,会直接 panic。所以,在不了解 channel 还有没有接收者的情况下,不能贸然关闭 channel。

    close 函数先上一把大锁,接着把所有挂在这个 channel 上的 sender 和 receiver 全都连成一个 sudog 链表,再解锁。最后,再将所有的 sudog 全都唤醒。

    唤醒之后,该干嘛干嘛。sender 会继续执行 chansend 函数里 goparkunlock 函数之后的代码,很不幸,检测到 channel 已经关闭了,panic。receiver 则比较幸运,进行一些扫尾工作后,返回。这里,selected 返回 true,而返回值 received 则要根据 channel 是否关闭,返回不同的值。如果 channel 关闭,received 为 false,否则为 true。这我们分析的这种情况下,received 返回 false。